package root.user;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import root.historic.Historic;
import root.item.Item;
import root.item.PublicarPedido;
import root.message.Message;
import root.message.MessageType;
import strategy.AbstractStrategyFactoryIF;
import strategy.GerenciadorAmizadeIF;
import strategy.GerenciadorEmprestimoIF;
import strategy.GerenciadorItemIF;
import strategy.GerenciadorMensagemIF;
import exceptions.AbsentAtributException;
import exceptions.BorrowDoesNotExist;
import exceptions.FriendshipDoesNotExist;
import exceptions.HasAlreadyDoneException;
import exceptions.InexistentCriterySortType;
import exceptions.InexistentSortType;
import exceptions.InexistentTopic;
import exceptions.InexistentType;
import exceptions.InvalidAtributException;
import exceptions.InvalidBorrowId;
import exceptions.InvalidCriterySortType;
import exceptions.InvalidDurationException;
import exceptions.InvalidItemCategoryException;
import exceptions.InvalidItemNameException;
import exceptions.InvalidKeyException;
import exceptions.InvalidMessage;
import exceptions.InvalidReceiver;
import exceptions.InvalidSender;
import exceptions.InvalidSortType;
import exceptions.InvalidSubject;
import exceptions.InvalidTopic;
import exceptions.InvalidType;
import exceptions.InvalidUserAddress;
import exceptions.InvalidUserException;
import exceptions.InvalidUserName;
import exceptions.InvalidUserUsername;
import exceptions.IsNotOnwerException;
import exceptions.ItemDoesNotExist;
import exceptions.ItemHasAlreadyBorrowedException;
import exceptions.ItemRequestAlreadyExistsException;
import exceptions.LoanHasAlreadyBeenApprovedException;
import exceptions.PermissionDeniedException;
import exceptions.WithoutPermissionRequestLoanException;
import exceptions.YouAreAlreadyFriends;
import exceptions.YouHaveAlreadyRequestedFriendship;
import exceptions.YouHaventReceivedARequest;
import geocode.Geocode;
import geocode.Geoinfo;

/**
 * Classe representante de um Usuario.
 */
public class User implements Comparable<User>, Location {
	private String username;
	private String name;
	private String address;
	private int borrowIds;
	private GerenciadorItemIF gerenciadorItem;
	private GerenciadorEmprestimoIF gerenciadorEmprestimo;
	private GerenciadorAmizadeIF gerenciadorAmizade;
	private GerenciadorMensagemIF gerenciadorMensagem;
	private Historic historic;
	private double latitude, longitude;
	
	public User(String username, String name, String address, AbstractStrategyFactoryIF factory) throws InvalidUserUsername, InvalidUserName, InvalidUserAddress {
		
		if (isInvalidUsername(username))
			throw new InvalidUserUsername("Login inválido");
		
		if (isInvalidName(name))
			throw new InvalidUserName("Nome inválido");
		
		if (isInvalidAddress(address))
			throw new InvalidUserAddress("Endereco inválido");
		this.borrowIds = 0;
		this.username = username;
		this.name = name;
		this.address = address;
		this.gerenciadorItem = factory.criaGerenciadorItem(this);
		this.gerenciadorEmprestimo = factory.criaGerenciadorEmprestimo(this, this.gerenciadorItem);
		this.gerenciadorMensagem = factory.criaGerenciadorMensagem(this);
		this.gerenciadorAmizade = factory.criaGerenciadorAmizade(this);
		this.historic = new Historic(this);
		addListener(this);
		setLocation();
		gerenciadorItem.addListener(gerenciadorMensagem);
		gerenciadorEmprestimo.addListener(gerenciadorMensagem);
	}
	
	private void setLocation() {
		Geoinfo info = new Geocode().getInfo(address);
		this.latitude = info.getLatitude();
		this.longitude = info.getLongitude();
	}
	/**
	 * retorna a latitude do usuario baseado em seu endereço
	 */
	public double getLatitude() {
		return latitude;
	}
	/**
	 * retorna a longitude do usuario baseado em seu endereço
	 */
	
	public double getLongitude() {
		return longitude;
	}
	/**
	 * Adiciona um listener de um determinando usuario em todos os seus gerenciadores.
	 * @param user
	 */
	public void addListener(User user){
		gerenciadorAmizade.addListener(user.historic);
		gerenciadorItem.addListener(user.historic);
		gerenciadorEmprestimo.addListener(user.historic);
	}

	private static boolean isInvalidAddress(String address) {
		if (address == null||address.isEmpty())
			return true;
		return false;
	}
	/**
	 * Retorna o Historico
	 * @return
	 */
	public Historic getHistoric(){
		return historic;
	}
	/**
	 * Retorna o historico do usuario na forma de String
	 * @return
	 */
	public String getHistorico(){
		return historic.getHistorico();
	}

	/**
	 * @return
	 * 	Numero de emprestimos completados ate o momento.
	 */
	public int getCompletedBorrows() {
		return borrowIds;
	}
	private static boolean isInvalidName(String name) {
		if (name == null||name.isEmpty())
			return true;
		return false;
	}

	private static boolean isInvalidUsername(String username) {
		if (username == null||username.isEmpty())
			return true;
		
		return false;
	}

	/**
	 * 
	 * @return
	 * 	username
	 */
	public String getUsername() {
		return username;
	}
	
	/**
	 * 
	 * @return
	 * 	nome do usuario
	 */
	public String getName() {
		return name;
	}
	
	/**
	 * 
	 * @return
	 *  address do usuario
	 */
	public String getAddress() {
		return address;
	}
	
	public boolean equals(Object obj) {
		if (!(obj instanceof User))
			return false;
		return getUsername().equals(( (User) obj).getUsername());
	}
	
	@Override
	public int hashCode() {
		return getUsername().hashCode();
	}
	
	@Override
	public String toString() {
		String resp="";
		resp=getName()+" - "+getAddress();
		
		return resp;
	}
	/**
	 * Incrementa em um o numero de emprestimos completados.
	 */
	public void incremetBorrows() {
		borrowIds++;
	}

	@Override
	/**
	 * CompareTo de usuário,
	 * this é menor que arg0 se tiver completado mais emprestimos.
	 */
	public int compareTo(User user) {
		return -getCompletedBorrows()+user.getCompletedBorrows();
	}
	/**
	 * Adiciona um determinado item na coleção de itens.
	 * @param owner
	 * Dono do item a ser adicionado 
	 * @param name
	 * Nome do item
	 * @param description
	 * Descrição do Item
	 * @param category
	 * Categoria do Item
	 * @return
	 * Id do item adicionado
	 * @throws InvalidItemNameException
	 * @throws InvalidItemCategoryException
	 */
	public String addItem(User owner, String name , String description, String category) throws InvalidItemNameException, InvalidItemCategoryException{
		return gerenciadorItem.addItem(owner, name, description, category);
	}
	/**
	 * deleta um determinado item de sua coleção a partir do objeto item
	 * @param item
	 * item a ser deletado
	 * @throws ItemDoesNotExist
	 * @throws IsNotOnwerException
	 * @throws PermissionDeniedException
	 */
	public void deleteItem(Item item) throws ItemDoesNotExist, IsNotOnwerException, PermissionDeniedException{
		gerenciadorItem.deleteItem(this, item);
		gerenciadorEmprestimo.removesRequest(item);
		for(User user : gerenciadorAmizade.getFriends())
			user.gerenciadorEmprestimo.removesRequest(item);
	}
	/**
	 * Deleta um item de sua coleção a partir do id de um determinado item
	 * @param itemId
	 * id do item a ser deletado
	 * @throws ItemDoesNotExist
	 * @throws IsNotOnwerException
	 * @throws PermissionDeniedException
	 */
	
	public void deleteItem(String itemId) throws ItemDoesNotExist, IsNotOnwerException, PermissionDeniedException{
		deleteItem(gerenciadorItem.getItem(itemId));
	}
	/**
	 * 
	 * @return
	 * retorna as descrições de todos os itens que o usuario possui
	 */
	public String getSelfItensDescription(){
		return gerenciadorItem.getItensName(getItems());
	}
	/**
	 * Retorna as descrições dos itens de seus amigos.
	 * @param friend
	 * Usuario a qual os itens vão ser descritos
	 * @return
	 * @throws PermissionDeniedException
	 */
	public String getFriendItensDescription(User friend) throws PermissionDeniedException{
		if(!gerenciadorAmizade.isFriend(friend))	throw new PermissionDeniedException("O usuário não tem permissão para visualizar estes itens");
		return gerenciadorItem.getItensName(friend.getItems());
	}
	/**
	 * 
	 * @return
	 * Todos os item que o usuario possui
	 */
	public Collection<Item> getItems(){
		return gerenciadorItem.getItems();
	}
	/**
	 * 
	 * @param user
	 * @param item
	 * @throws ItemDoesNotExist
	 * @throws InvalidAtributException
	 * @throws PermissionDeniedException
	 * @throws HasAlreadyDoneException
	 */
	public void addItemInterest(User user, Item item) throws ItemDoesNotExist, InvalidAtributException, PermissionDeniedException, HasAlreadyDoneException{
		gerenciadorItem.addItemInterest(this, user.gerenciadorItem, item);
	}
	/**
	 * Adiciona o usuario como interessado no item de id {@code itemId} de um de seus amigos
	 * @param itemId
	 * @throws ItemDoesNotExist
	 * @throws InvalidAtributException
	 * @throws PermissionDeniedException
	 * @throws HasAlreadyDoneException
	 */
	public void addItemInterest(String itemId) throws ItemDoesNotExist, InvalidAtributException, PermissionDeniedException, HasAlreadyDoneException{
		addItemInterest(this,gerenciadorAmizade.getItem(itemId));
	}
	/**
	 * 
	 * Usuário pesquisa por itens baseado em uma chave e o atributo a qual a chave se refere<br>
	 * O resultado da pesquisa vem ordenado de acordo como o {@code tipoOrdenacao} e o {@code criterioOrdenacao}
	 * 
	 * @param chave
	 * @param atributo
	 * @param tipoOrdenacao
	 * @param criterioOrdenacao
	 * @return
	 * @throws InvalidKeyException
	 * @throws InvalidSortType
	 * @throws InexistentSortType
	 * @throws InexistentCriterySortType
	 * @throws InvalidCriterySortType
	 * @throws InvalidAtributException
	 * @throws AbsentAtributException
	 * @throws ItemDoesNotExist
	 */
	public String getSearchItemsString(String chave, String atributo,String tipoOrdenacao, String criterioOrdenacao) throws InvalidKeyException, InvalidSortType, InexistentSortType, InexistentCriterySortType, InvalidCriterySortType, InvalidAtributException, AbsentAtributException, ItemDoesNotExist{
		List<Item> list = new ArrayList<Item>();
		list.addAll(getItems());
		for (User user : gerenciadorAmizade.getFriends()) 
			list.addAll(user.getItems());
		return gerenciadorItem.searchItems(list, chave, atributo, tipoOrdenacao, criterioOrdenacao);
	}
	/**
	 * Usuário pede o item {@code item} emprestado ao seu dono
	 * 
	 * @param item
	 * item a ser emprestado
	 * @param days
	 * @return
	 * @throws ItemDoesNotExist
	 * @throws ItemRequestAlreadyExistsException
	 * @throws WithoutPermissionRequestLoanException
	 * @throws InvalidDurationException
	 * @throws InvalidAtributException
	 * @throws AbsentAtributException
	 * @throws InvalidMessage
	 * @throws InvalidSubject
	 * @throws InvalidReceiver
	 * @throws IsNotOnwerException
	 */
	public String borrowItem(Item item, int days) throws ItemDoesNotExist, ItemRequestAlreadyExistsException, WithoutPermissionRequestLoanException, InvalidDurationException, InvalidAtributException, AbsentAtributException, InvalidMessage, InvalidSubject, InvalidReceiver, IsNotOnwerException {
		return gerenciadorEmprestimo.borrowItem(item, days, gerenciadorItem.getOwner(item).gerenciadorEmprestimo);
	}
	/**
	 * empresta um item a partir de um id 
	 * @param itemId
	 * @param days
	 * @return
	 * @throws ItemDoesNotExist
	 * @throws ItemRequestAlreadyExistsException
	 * @throws WithoutPermissionRequestLoanException
	 * @throws InvalidDurationException
	 * @throws InvalidAtributException
	 * @throws AbsentAtributException
	 * @throws InvalidMessage
	 * @throws InvalidSubject
	 * @throws InvalidReceiver
	 * @throws IsNotOnwerException
	 */
	public String borrowItem(String itemId, int days) throws ItemDoesNotExist, ItemRequestAlreadyExistsException, WithoutPermissionRequestLoanException, InvalidDurationException, InvalidAtributException, AbsentAtributException, InvalidMessage, InvalidSubject, InvalidReceiver, IsNotOnwerException{
		Item item = gerenciadorAmizade.getItem(itemId);
		return borrowItem(item, days);
	}
	/**
	 * retorna um pedido de um item a partir de um id
	 * @param pedidoId
	 * @return
	 * @throws InvalidAtributException
	 */
	public PublicarPedido getPedidoItem(String pedidoId) throws InvalidAtributException{
		return gerenciadorItem.getPedidoItem(pedidoId);
	}
	/**
	 * Publica Um pedido
	 * @param nomeItem
	 * Nome do item para o qual se deseja publicar o pedido
	 * @param descricaoItem
	 * descrição do item 
	 * @return
	 * id da publicação do pedido
	 * @throws InvalidAtributException
	 */
	public String publicarPedido(String nomeItem, String descricaoItem) throws InvalidAtributException{
		return gerenciadorItem.addPublicarPedido(this, nomeItem, descricaoItem);
	}
	/**
	 * Republica um dado pedido
	 * @param idPedido
	 * id do pedido para ser republicado
	 * @throws InvalidAtributException
	 */
	public void rePublicarPedido(String idPedido) throws InvalidAtributException{
		PublicarPedido pedido = gerenciadorAmizade.getPedidoItem(idPedido);
		gerenciadorItem.rePublicarPedido(pedido);
	}
	/**
	 * Oferece um determinado item para um dado pedido
	 * @param pedido
	 * @param itemId
	 * id do item a ser oferecido
	 * @throws InvalidAtributException
	 * @throws ItemDoesNotExist
	 * @throws InvalidSubject 
	 * @throws InvalidMessage 
	 * @throws InvalidReceiver 
	 * @throws InvalidSender 
	 */
	public void oferecerItem(PublicarPedido pedido, String itemId) throws InvalidAtributException, ItemDoesNotExist, InvalidSender, InvalidReceiver, InvalidMessage, InvalidSubject{
		gerenciadorItem.oferecerItem(this, pedido, itemId);
	}
	/**
	 * 
	 * @param itemI
	 * id do item a ser retornado
	 * @return
	 * Item
	 * @throws ItemDoesNotExist
	 */
	public Item getItem(String itemId) throws ItemDoesNotExist{
		return gerenciadorItem.getItem(itemId);
	}
	/**
	 * Retorna um atributo de determinado item
	 * 
	 * @param item
	 * item para o qual o atributo vai ser retornado
	 * @param atributo
	 * @return
	 * Atributo do item
	 * @throws InvalidAtributException
	 * @throws AbsentAtributException
	 * @throws ItemDoesNotExist
	 */
	
	public String getAtributoItem(Item item, String atributo) throws InvalidAtributException, AbsentAtributException, ItemDoesNotExist{
		return gerenciadorItem.getAtributoItem(item, atributo);
	}
	
	/**
	 * Devolve um item a partir do id de emprestimo
	 * 
	 * @param borrowId
	 * id do emprestimo para ser devolvido
	 * @throws PermissionDeniedException
	 * @throws HasAlreadyDoneException
	 * @throws BorrowDoesNotExist
	 * @throws InvalidBorrowId
	 */
	public void returnItem(String borrowId) throws PermissionDeniedException, HasAlreadyDoneException, BorrowDoesNotExist, InvalidBorrowId{
		gerenciadorEmprestimo.returnItem(borrowId);
	}
	/**
	 * Aprova um emprestimo a partir de um id
	 * 
	 * @param borrowId id do emprestimo a ser aprovado
	 * @return
	 * @throws IsNotOnwerException
	 * @throws BorrowDoesNotExist
	 * @throws ItemDoesNotExist
	 * @throws LoanHasAlreadyBeenApprovedException
	 * @throws ItemHasAlreadyBorrowedException
	 * @throws InvalidBorrowId
	 * @throws PermissionDeniedException
	 */
	public String approveLoan(String borrowId) throws IsNotOnwerException, BorrowDoesNotExist, ItemDoesNotExist, LoanHasAlreadyBeenApprovedException, ItemHasAlreadyBorrowedException, InvalidBorrowId, PermissionDeniedException{
		return gerenciadorEmprestimo.approveLoan(borrowId);
	}
	
	/**
	 * Comfirmar o termino de um emperstimo
	 * @param borrowId
	 * id do emprestimo a ser terminado 
	 * @throws PermissionDeniedException
	 * @throws HasAlreadyDoneException
	 * @throws ItemDoesNotExist
	 * @throws InvalidBorrowId
	 * @throws BorrowDoesNotExist
	 */
	public void completeLoan(String borrowId) throws PermissionDeniedException, HasAlreadyDoneException, ItemDoesNotExist, InvalidBorrowId, BorrowDoesNotExist{
		gerenciadorEmprestimo.completeLoan(borrowId);
	}
	
	/**
	 * Requisitar devolução de um item de determinado emprestimo
	 * @param borrowId id do emprestimo a ser requisitado a devolução
	 * @throws BorrowDoesNotExist
	 * @throws InvalidBorrowId
	 * @throws HasAlreadyDoneException
	 * @throws PermissionDeniedException
	 * @throws ItemDoesNotExist
	 * @throws InvalidAtributException
	 * @throws AbsentAtributException
	 * @throws InvalidMessage
	 * @throws InvalidSubject
	 * @throws InvalidReceiver
	 */
	public void requestDevolution(String borrowId) throws BorrowDoesNotExist, InvalidBorrowId, HasAlreadyDoneException, PermissionDeniedException, ItemDoesNotExist, InvalidAtributException, AbsentAtributException, InvalidMessage, InvalidSubject, InvalidReceiver{
		gerenciadorEmprestimo.requestDevolution(borrowId);
	}
	/**
	 * 
	 * @param atributo
	 * @return Uma descrição do emprestimos em que o usuário está envolvido
	 * @throws InvalidAtributException
	 * @throws AbsentAtributException
	 * @throws ItemDoesNotExist
	 */
	public String getEmprestimos(String atributo) throws InvalidAtributException, AbsentAtributException, ItemDoesNotExist{
		return gerenciadorEmprestimo.getEmprestimos(atributo);
	}
	/**
	 * Se certifica que o usuário está envolvido no empréstimo de id {@code borrowId}
	 * @param borrowId
	 * @throws Exception
	 */
	public void belongsLoan(String borrowId) throws Exception{
		gerenciadorEmprestimo.belongsLoan(borrowId);
	}
	
	/**
	 * Apaga todas as requisições de emprestimo de itens <br>
	 * em que o usuário e {@code user2} estão envolvidos
	 * @param user2
	 */
	public void removesAllRequestsBetween(User user2){
		gerenciadorEmprestimo.removesAllRequestsBetween(user2);
	}
	/**
	 * Termina uma amizade entre o usuario e {@code user}
	 * @param user usuario para o qual a amizade será terminada
	 * @throws FriendshipDoesNotExist se os dois não são amigos
	 */
	public void eraseFriendship(User user) throws FriendshipDoesNotExist{
		gerenciadorAmizade.eraseFriendship(user.gerenciadorAmizade);
	}
	
	/**
	 * Comfirma um pedido de amizade do {@code user}
	 * 
	 * @param user usuario para o qual a amizade será comfirmada
	 * @throws YouHaventReceivedARequest
	 * @throws YouAreAlreadyFriends
	 * @throws InvalidUserException
	 */
	public void acceptFriendship(User user) throws YouHaventReceivedARequest, YouAreAlreadyFriends, InvalidUserException{
		gerenciadorAmizade.acceptFriendship(user.gerenciadorAmizade);
		addListener(user);
		user.addListener(this);
	}
	/**
	 * Usuario pergunta se {@code user} deseja ser seu amizade
	 * @param user
	 * usuario a qual a amizade será requisitada
	 * @throws YouHaveAlreadyRequestedFriendship
	 * @throws YouAreAlreadyFriends
	 * @throws InvalidUserException
	 */
	public void requestFriendship(User user) throws YouHaveAlreadyRequestedFriendship, YouAreAlreadyFriends, InvalidUserException{
		gerenciadorAmizade.requestFriendShip(user.gerenciadorAmizade);
	}
	/**
	 * Verifica se {@code user} é seu amigo
	 * @param user
	 * user a sér verificado
	 * @return
	 */
	public boolean isFriend(User user){
		return gerenciadorAmizade.isFriend(user);
	}
	/**
	 * 
	 * @return
	 * Lista de amigos
	 */
	public Collection<User> getFriends(){
		return gerenciadorAmizade.getFriends();
	}
	
	/**
	 * 
	 * @return Uma descrição de todas as requisições de amizade feitas para o usúario
	 */
	public String getFriendshipRequests(){
		return gerenciadorAmizade.getFriendshipRequestsStringMode();
	}
	
	/**
	 * 
	 * @return Uma descrição de todos os amigos do usuário
	 */
	public String getFriendsStringMode(){
		return gerenciadorAmizade.getFriendshipStringMode();
	}
	
	/**
	 * 
	 * @param user
	 * @return Uma descrição de todos os amigos de um {@code user} que é seu amigo
	 */
	public String getFriendsStringMode(User user){
		return user.gerenciadorAmizade.getFriendshipStringMode();
	}
	/**
	 * 
	 * @return Historico de atividades em conjunto suas e de seus amigos
	 */

	public String getHistoricoAtividadesConjunto() {
		return historic.getHistoricoAtividadesConjunto();
	}
	
	
	public boolean hasBorrow(String borrowId){
		return gerenciadorEmprestimo.hasBorrow(borrowId);
	}
	
	public void addMessage(Message message){
		gerenciadorMensagem.addMessage2(message);
	}
	
	public String createMessage(User dest, String assunto,String  mensagem,MessageType type) throws InvalidSender, InvalidReceiver, InvalidMessage, InvalidSubject{
		return gerenciadorMensagem.createMessage(dest, assunto, mensagem, type);
	}
	
	public String createMessage(User destinatario, String assunto, String mensagem,String idRequisicaoEmprestimo) throws Exception{
		return gerenciadorMensagem.createMessage(destinatario, assunto, mensagem, idRequisicaoEmprestimo);
	}
	
	public String getTopics(String tipo) throws InvalidType, InexistentType{
		return gerenciadorMensagem.getTopics(tipo);
	}
	
	public String getStringMessages(String idTopico) throws InvalidTopic, InexistentTopic{
		return gerenciadorMensagem.getStringMessages(idTopico);
	}
}
